#include <cstring>
#include <cstdlib>
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>

// ================================================================================

class SNMPSession {
public:
  SNMPSession(char* peer) { 
    snmp_sess_init(&_cfg);
    _cfg.peername = peer;
    _cfg.version = SNMP_VERSION_1;
    _cfg.timeout = 1000000;
    _cfg.retries = 2;
    _cfg.community = (u_char*)"public";
    _cfg.community_len = 6;

    _os = snmp_open(&_cfg);
    if (_os == NULL) {
      snmp_perror("Failed to open an SNMP session:");
      abort();
    }

    _nfirst = false;
    _pdu = NULL;
    _response = NULL;
  }

  ~SNMPSession() {
    if (_response) {
      snmp_free_pdu(_response);
    }

    if (_pdu) {
      snmp_free_pdu(_pdu);
    }

    if (_os) {
      snmp_close(_os);
    }
  }

  int start(const char* firstOid) {
    /*
    if (read_objid(firstOid, _firstOid, &_firstOidLen) == 0) {
      snmp_perror("Failed to parse an OID!");
      return -1;
    }
    */

    parse_objid(firstOid, _firstOid, &_firstOidLen);

    resetPDU();
    snmp_add_null_var(_pdu, _firstOid, _firstOidLen);

    _response = NULL;

    return 0;
  }

  void resetPDU() {
    if (_pdu) {
      snmp_free_pdu(_pdu);
      _pdu = NULL;
    }

    createPDU();
  }

  bool createPDU() {
    if (_pdu == NULL) {
      _pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
      if (_pdu == NULL) {
        snmp_perror("Failed to create a PDU:");
        abort();
        return false;
      } 
    }
    return true;
  }


  netsnmp_variable_list* next() {
    netsnmp_variable_list* ret;

    if (!_pdu) {
      return NULL;
    }

    ret = NULL;
    if (_response) {
      snmp_free_pdu(_response);
    }

    if (snmp_synch_response(_os, _pdu, &_response) == STAT_SUCCESS) {
      _pdu = NULL;

      if (!_response || !_response->variables) {
        return NULL;
      }

      if (is_prefix(_firstOid, _firstOidLen, _response->variables->name, _response->variables->name_length) == 0) {
        return NULL;
      }      

      if (!createPDU()) {
        return NULL;
      }

      snmp_add_null_var(_pdu, _response->variables->name, _response->variables->name_length);      

      ret = _response->variables;
    } else {
      _pdu = NULL;
      return NULL;
    }

    //  cleanup_response:
    return ret;
  }

private:
  static void parse_objid(const char* sobjid, oid* objid, size_t* len) {
    size_t slen = strlen(sobjid);
    char* sp;
    size_t i = 0;

    char* s = new char[slen + 1];
    strcpy(s, sobjid);

    char* p = s + 1;
    strtok_r(p, ".", &sp);
    while (p) {
      sscanf(p, "%ld", &objid[i]);
      i++;
      p = strtok_r(NULL, ".", &sp);
    }

    delete [] s;
    *len = i;
  }

  static bool is_prefix(const oid* oid1, size_t oid1_len, const oid* oid2, size_t oid2_len) {
    if (oid1_len > oid2_len) {
      return false;
    }

    for (size_t i = 0; i < oid1_len; ++i) {
      if (oid1[i] != oid2[i]) {
        return false;
      }
    }

    return true;
  }

private:
  oid _firstOid[MAX_OID_LEN];
  size_t _firstOidLen;
  netsnmp_session _cfg;
  netsnmp_session* _os;
  netsnmp_pdu* _pdu;
  netsnmp_pdu* _response;

  bool _nfirst;
};
